import BehaviorStore, { behaviorStack } from './behaviorStore';
import { proxyXmlHttp, proxyFetch, httpMetrics } from './userVitals';

// TODO React 异常捕获
// TODO 类型抽取

// 错误类型
export enum mechanismType {
  JS = 'js',
  RS = 'resource',
  UJ = 'unhandledrejection',
  HP = 'http',
  CS = 'cors',
  VUE = 'vue',
}

// 格式化后的 异常数据结构体
export interface ExceptionMetrics {
  mechanism: Object;
  value?: string;
  type: string;
  stackTrace?: Object;
  pageInformation?: Object;
  breadcrumbs?: Array<behaviorStack>;
  errorUid: string;
  meta?: any;
}

// 初始化用参
export interface ErrorVitalsInitOptions {
  Vue: any;
}

// 判断是 JS异常、静态资源异常、还是跨域异常
export const getErrorKey = (event: ErrorEvent | Event) => {
  const isJsError = event instanceof ErrorEvent;
  if (!isJsError) return mechanismType.RS;
  return event.message === 'Script error.'
    ? mechanismType.CS
    : mechanismType.JS;
};
// 对每一个错误详情，生成一串编码
export const getErrorUid = (input: string) => {
  return window.btoa(unescape(encodeURIComponent(input)));
};
// 正则表达式，用以解析堆栈split后得到的字符串
const FULL_MATCH =
  /^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|address|native|eval|webpack|<anonymous>|[-a-z]+:|.*bundle|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;

// 限制只追溯10个
const STACKTRACE_LIMIT = 10;

// 解析每一行
export function parseStackLine(line: string) {
  const lineMatch = line.match(FULL_MATCH);
  if (!lineMatch) return {};
  const filename = lineMatch[2];
  const functionName = lineMatch[1] || '';
  const lineno = parseInt(lineMatch[3], 10) || undefined;
  const colno = parseInt(lineMatch[4], 10) || undefined;
  return {
    filename,
    functionName,
    lineno,
    colno,
  };
}
// 静态资源错误的 ErrorTarget
export interface ResourceErrorTarget {
  src?: string;
  tagName?: string;
  outerHTML?: string;
}

// 解析错误堆栈
export function parseStackFrames(error: Error) {
  const { stack } = error;
  // 无 stack 时直接返回
  if (!stack) return [];
  const frames = [];
  for (const line of stack.split('\n').slice(1)) {
    const frame = parseStackLine(line);
    if (frame) {
      frames.push(frame);
    }
  }
  return frames.slice(0, STACKTRACE_LIMIT);
}
export interface Vue {
  config: {
    errorHandler?: any;
    warnHandler?: any;
  };
}

export interface ViewModel {
  _isVue?: boolean;
  __isVue?: boolean;
  $root: ViewModel;
  $parent?: ViewModel;
  $props: { [key: string]: any };
  $options: {
    name?: string;
    propsData?: { [key: string]: any };
    _componentTag?: string;
    __file?: string;
  };
}

// 获取报错组件名
const classifyRE = /(?:^|[-_])(\w)/g;
const classify = (str: string) =>
  str.replace(classifyRE, (c) => c.toUpperCase()).replace(/[-_]/g, '');
const ROOT_COMPONENT_NAME = '<Root>';
const ANONYMOUS_COMPONENT_NAME = '<Anonymous>';
export const formatComponentName = (vm: ViewModel, includeFile: Boolean) => {
  if (!vm) {
    return ANONYMOUS_COMPONENT_NAME;
  }
  if (vm.$root === vm) {
    return ROOT_COMPONENT_NAME;
  }
  const options = vm.$options;
  let name = options.name || options._componentTag;
  const file = options.__file;
  if (!name && file) {
    const match = file.match(/([^/\\]+)\.vue$/);
    if (match) {
      name = match[1];
    }
  }
  return (
    (name ? `<${classify(name)}>` : ANONYMOUS_COMPONENT_NAME) +
    (file && includeFile !== false ? ` at ${file}` : '')
  );
};

// 初始化的类
export default class   {
  private engineInstance: any;

  // 已上报的错误 uid
  private submitErrorUids: Array<string>;

  constructor(engineInstance: any, options: ErrorVitalsInitOptions) {
    console.log(engineInstance);
    
    
    const { Vue } = options;
    this.engineInstance = engineInstance;
    this.submitErrorUids = [];
    // 初始化 js错误
    this.initJsError();
    // 初始化 静态资源加载错误
    this.initResourceError();
    // 初始化 Promise异常
    this.initPromiseError();
    // 初始化 HTTP请求异常
    this.initHttpError();
    // 初始化 跨域异常
    this.initCorsError();
    // 初始化 Vue异常
    // this.initVueError(Vue);
  }
  errorSendHandler = (data: ExceptionMetrics) => {
    console.log( "js异常",data);
  }


  // 初始化 JS异常 的数据获取和上报
  initJsError = (): void => {    
    const handler = (event: ErrorEvent) => {
      // 阻止向上抛出控制台报错
      event.preventDefault();
      // 如果不是 JS异常 就结束

      if (getErrorKey(event) !== mechanismType.JS) return;
      const exception = {
        // 上报错误归类
        mechanism: {
          type: mechanismType.JS,
        },
        // 错误信息
        value: event.message,
        // 错误类型
        type: (event.error && event.error.name) || 'UnKnowun',
        // 解析后的错误堆栈
        stackTrace: {
          frames: parseStackFrames(event.error),
        },
        // 用户行为追踪 breadcrumbs 在 errorSendHandler 中统一封装
        // 页面基本信息 pageInformation 也在 errorSendHandler 中统一封装
        // 错误的标识码
        errorUid: getErrorUid(
          `${mechanismType.JS}-${event.message}-${event.filename}`
        ),
        // 附带信息
        meta: {
          // file 错误所处的文件地址
          file: event.filename,
          // col 错误列号
          col: event.colno,
          // row 错误行号
          row: event.lineno,
        },
      } as ExceptionMetrics;
      // 一般错误异常立刻上报，不用缓存在本地
      
      this.errorSendHandler(exception);
    };
    window.addEventListener('error', (event) => handler(event), true);
  };

  // 初始化 静态资源异常 的数据获取和上报
  initResourceError = (): void => {
    const handler = (event: Event) => {
      event.preventDefault(); // 阻止向上抛出控制台报错
      // 如果不是跨域脚本异常,就结束
      if (getErrorKey(event) !== mechanismType.RS) return;
      const target = event.target as ResourceErrorTarget;
      const exception = {
        // 上报错误归类
        mechanism: {
          type: mechanismType.RS,
        },
        // 错误信息
        value: '',
        // 错误类型
        type: 'ResourceError',
        // 用户行为追踪 breadcrumbs 在 errorSendHandler 中统一封装
        // 页面基本信息 pageInformation 也在 errorSendHandler 中统一封装
        // 错误的标识码
        errorUid: getErrorUid(
          `${mechanismType.RS}-${target.src}-${target.tagName}`
        ),
        // 附带信息
        meta: {
          url: target.src,
          html: target.outerHTML,
          type: target.tagName,
        },
      } as ExceptionMetrics;
      // 一般错误异常立刻上报，不用缓存在本地
      this.errorSendHandler(exception);
    };
    window.addEventListener('error', (event) => handler(event), true);
  };

  // 初始化 Promise异常 的数据获取和上报
  initPromiseError = (): void => {
    const handler = (event: PromiseRejectionEvent) => {
      event.preventDefault(); // 阻止向上抛出控制台报错
      const value = event.reason.message || event.reason;
      const type = event.reason.name || 'UnKnowun';
      const exception = {
        // 上报错误归类
        mechanism: {
          type: mechanismType.UJ,
        },
        // 错误信息
        value,
        // 错误类型
        type,
        // 解析后的错误堆栈
        stackTrace: {
          frames: parseStackFrames(event.reason),
        },
        // 用户行为追踪 breadcrumbs 在 errorSendHandler 中统一封装
        // 页面基本信息 pageInformation 也在 errorSendHandler 中统一封装
        // 错误的标识码
        errorUid: getErrorUid(`${mechanismType.UJ}-${value}-${type}`),
        // 附带信息
        meta: {},
      } as ExceptionMetrics;
      // 一般错误异常立刻上报，不用缓存在本地
      this.errorSendHandler(exception);
    };

    window.addEventListener(
      'unhandledrejection',
      (event) => handler(event),
      true
    );
  };

  // 初始化 HTTP请求异常 的数据获取和上报
  initHttpError = (): void => {
    const loadHandler = (metrics: httpMetrics) => {
      // 如果 status 状态码小于 400,说明没有 HTTP 请求错误
      if (metrics.status < 400) return;
      const value = metrics.response;
      const exception = {
        // 上报错误归类
        mechanism: {
          type: mechanismType.HP,
        },
        // 错误信息
        value,
        // 错误类型
        type: 'HttpError',
        // 错误的标识码
        errorUid: getErrorUid(
          `${mechanismType.HP}-${value}-${metrics.statusText}`
        ),
        // 附带信息
        meta: {
          metrics,
        },
      } as ExceptionMetrics;
      // 一般错误异常立刻上报，不用缓存在本地
      this.errorSendHandler(exception);
    };
    proxyXmlHttp(null, loadHandler);
    proxyFetch(null, loadHandler);
  };

  // 初始化 跨域异常 的数据获取和上报
  initCorsError = (): void => {
    const handler = (event: ErrorEvent) => {
      // 阻止向上抛出控制台报错
      event.preventDefault();
      // 如果不是跨域脚本异常,就结束
      if (getErrorKey(event) !== mechanismType.CS) return;
      const exception = {
        // 上报错误归类
        mechanism: {
          type: mechanismType.CS,
        },
        // 错误信息
        value: event.message,
        // 错误类型
        type: 'CorsError',
        // 错误的标识码
        errorUid: getErrorUid(`${mechanismType.JS}-${event.message}`),
        // 附带信息
        meta: {},
      } as ExceptionMetrics;
      // 自行上报异常，也可以跨域脚本的异常都不上报;
      this.errorSendHandler(exception);
    };
    window.addEventListener('error', (event) => handler(event), true);
  };
  // 只需要在外部把初始化好的 Vue 对象传入即可~
  // 初始化 Vue异常 的数据获取和上报
  initVueError = (app: any): void => {
    app.config.errorHandler = (
      err: Error,
      vm: ViewModel,
      info: string
    ): void => {
      const componentName = formatComponentName(vm, false);
      const exception = {
        // 上报错误归类
        mechanism: {
          type: mechanismType.VUE,
        },
        // 错误信息
        value: err.message,
        // 错误类型
        type: err.name,
        // 解析后的错误堆栈
        stackTrace: {
          frames: parseStackFrames(err),
        },
        // 错误的标识码
        errorUid: getErrorUid(
          `${mechanismType.JS}-${err.message}-${componentName}-${info}`
        ),
        // 附带信息
        meta: {
          // 报错的Vue组件名
          componentName,
          // 报错的Vue阶段
          hook: info,
        },
      } as ExceptionMetrics;
      // 一般错误异常立刻上报，不用缓存在本地
      this.errorSendHandler(exception);
    };
  };
}
